package com.lemons.hackaton.algorithm;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;

import com.lemons.hackaton.app.data.OutputWriter;
import com.lemons.hackaton.app.util.SortAscendingUtil;
import com.lemons.hackaton.app.util.SortUtil;
import com.lemons.hackaton.obj.Journey;

public class Scheduler {

	public Journey journey;
	public Map<String, Person> allPersons;
	public Map<String, Activity> allActivities;
	
	/* init stuff **************************************************/
	
	public Scheduler(Journey journey){
		this.journey = journey;
		allPersons = initPersons();
		allActivities = initActivities();
	}
	
	public Map<String, Person> initPersons(){
		List<com.lemons.hackaton.obj.Person> journeyPersons = journey.getPersons();
		Map<String, Person> persons = new LinkedHashMap<String, Person>();
		for (com.lemons.hackaton.obj.Person jp : journeyPersons){
			Person person = new Person();
			person.setFields(jp);
			person.setCurrentBudget(jp.getBudget());
			person.setCurrentAwakeTime(0);
			person.setCurrentSleepTime(0);
			persons.put(jp.getName(), person);
		}
		return persons;
	}
	
	public Map<String, Activity> initActivities(){
		List<com.lemons.hackaton.obj.Activity> journeyActivities = journey.getActivities();
		Map<String, Activity> activities = new LinkedHashMap<String, Activity>();
		for (com.lemons.hackaton.obj.Activity ja : journeyActivities){
			Activity activity = new Activity();
			activity.setFields(ja);
			activity.setCurrentMaintenanceHours(0);
			activity.setCurrentOpenHours(0);
			activities.put(ja.getName(), activity);
		}
		return activities;
	}
	
	/*  stuff **************************************************/
	
	public Float getMaximumProfit(){
		
		Float maximumProfit = 0F;
		
		for (Person person : allPersons.values()){
			maximumProfit += person.getBudget();
		}
		
		return maximumProfit;
	} 
	
	public Boolean isPersonAvailableForActivity(Person person, Activity activity){
		//return (person.getCurrentAwakeTime() < person.getMaxAwakeTime()) && (person.getCurrentBudget() >= activity.getTicketPrice()) && (!person.hasCurrentActivity() && availablePersons.containsKey(person.getName()));
		return (person.getCurrentAwakeTime() < person.getMaxAwakeTime()) && (person.getCurrentBudget() >= activity.getTicketPrice()) && (availablePersons.containsKey(person.getName()));
	}
	
	public List<String> getPersonsWhoLikeActivity(Activity activity){
		
		List<String> persons = new ArrayList<String>();

		for (Person person : availablePersons.values()){
			if (person.likesActivity(activity.getName()) && isPersonAvailableForActivity(person, activity)){
				persons.add(person.getName());
			}
		}
		
		//System.out.println("Persons who like: " + activity.getName() + " (price = " + activity.getTicketPrice() + "):" + persons.size());
		
		return persons;
	}
	
	public Boolean canActivityBeDone(Activity activity){
		return (getPersonsWhoLikeActivity(activity).size() >= activity.getMinNoOfParticipants()) && (activity.isOpen());	
	}
		
	public Float getMaximumProfitPerActivity(Activity activity){
		
		Integer personsWhoLikeActivity = getPersonsWhoLikeActivity(activity).size();
		
		//System.out.println("Persons who like " + activity.getName() + ": " + personsWhoLikeActivity);
		
		if (personsWhoLikeActivity < activity.getMinNoOfParticipants()){
			return -1.0F;
		}
		
		return (activity.getCapacity() < personsWhoLikeActivity ? activity.getCapacity() : personsWhoLikeActivity) * activity.getTicketPrice();
		
	}

	/* algorithm *******************************************************************/
	
	public Map<String, Activity> availableActivities = new HashMap<String, Activity>();
	public Map<String, Person> availablePersons = new HashMap<String, Person>();
	
	List<Schedule> schedule = new ArrayList<Schedule>();
	
	Integer currentHour = 0;
	
	public void sortByProfit(){
		
		Map<String, Float> potentialProfit = new HashMap<String, Float>();
		
		for (Activity a : availableActivities.values()){
			potentialProfit.put(a.getName(), getMaximumProfitPerActivity(a));
		}
		
		Map<String, Float> result = SortUtil.sortByValue(potentialProfit);
		for (Map.Entry<String, Float> e : result.entrySet()){
			System.out.println(e.getKey() + ":" + e.getValue());
		}
	}
	
	public Activity chooseBestActivity(){
		
		System.out.println("Choosing best activity... ");
		
		Float maxProfit = 0F;
		Activity bestActivity = null;
		
		for (Activity activity : availableActivities.values()){
			Float profit = getMaximumProfitPerActivity(activity);
			if (profit > maxProfit){
				maxProfit = profit;
				bestActivity = activity;
			}
		}
		
		return bestActivity;
		
	}
	
	public Activity chooseFirstActivity(){
		
		Float maxProfit = 0F;
		Activity activity = null;
		
		for (Activity a : allActivities.values()){
			Float profit = getMaximumProfitPerActivity(a);
			if (profit > maxProfit){
				maxProfit = profit;
				activity = a;
			}
		}
		
		System.out.println("Max profit: " + maxProfit + ", activity: " + activity.getName());
		return activity;
	}
	
	public void makeAllPersonsAvailable(){
		System.out.println("Making all persons available.");
		for (Person person : allPersons.values()){
			availablePersons.put(person.getName(), person);
		}
	}
	
	public void makeAllActivitiesAvailable(){
		System.out.println("Making all activities available.");
		for (Activity activity : allActivities.values()){
			availableActivities.put(activity.getName(), activity);
		}
	}
	
	
	public void printAvailablePersons(){
		System.out.println("Available persons: ");
		for (Person p : allPersons.values()){
			if (availablePersons.containsKey(p.getName())){
				System.out.println(p);
			}
		}
	}
	
	public void printAvailableActivities(){
		System.out.println("Available activities: ");
		for (Activity a : allActivities.values()){
			if (availableActivities.containsKey(a.getName())){
				System.out.println(a);
			}
		}
	}
	
	
	public void printCurrentSchedule(){
		System.out.println("Current schedule: ");
		
		
		for (Schedule s : schedule){
			if (s.getPersons().contains("P2")) {
			System.out.println(s.getHour());
			System.out.println(s.getActivity());
			System.out.println(s.getProfit());
			//System.out.println("persons:: " + s.getPersons().size());
			
//			System.out.println("Persons: ");
//			for (String p : s.getPersons()) {
//				System.out.println(allPersons.get(p));
//			}
			}
		}
	}
	
	public void addPersonToActivity(String person, String activity){
		Person p = allPersons.get(person); 
		if (p.isSleeping() == null) {
			p.wakeUp();
		}
		
		p.setCurrentBudget(p.getCurrentBudget() - allActivities.get(activity).getTicketPrice());
		p.setCurrentActivity(activity);

		allPersons.put(person, p);
		availablePersons.remove(person);
	} 
	
	public Set<String> sortAvailablePersonsForActivity(String activity, List<String> persons){
		
		Map<String, Integer> currentPersons = new HashMap<String, Integer>();
		
		for (String s : persons){
			Person p = allPersons.get(s);
			if (p != null){
				Set<String> prefs = p.getPreferences();
				int countPrefs = 0;
				for (String pref : prefs){
					if (availableActivities.containsKey(pref)){
						countPrefs++;
					}
				}
				currentPersons.put(s, countPrefs);
			}
		}
		
		currentPersons = SortAscendingUtil.sortByValue(currentPersons);
		
//		for (Map.Entry<String, Integer> e : currentPersons.entrySet()){
//			System.out.println(e.getKey() + ": " + e.getValue());
//		}
//		
		return currentPersons.keySet();
	}
	
	public void scheduleActivity(Integer hour, String activity, List<String> persons){
		
		System.out.println("Scheduling activity " + activity + " for hour " + hour);
		
		Schedule entry = new Schedule();
		
		Activity currentActivity = allActivities.get(activity);
	
		if (currentActivity.isOpen() == null) { 
			currentActivity.openForPersons();
		}
		
		entry.setHour(hour);
		entry.setActivity(currentActivity);
		entry.setActivityId(activity);
		
		Integer currentNoOfPeople = 0;
		
		List<String> scheduledPersons = new ArrayList<String>();
		
		Set<String> sortedPersons = sortAvailablePersonsForActivity(activity, persons);
		
		for (String person : sortedPersons){
			if (currentActivity.getCapacity() > currentNoOfPeople){
				addPersonToActivity(person, activity);
				scheduledPersons.add(person);
				currentNoOfPeople++;
			} else {
				break;
			}
		}
		
		entry.setProfit(currentNoOfPeople * currentActivity.getTicketPrice());
		entry.setPersons(scheduledPersons);
		
		allActivities.put(activity, currentActivity);
		availableActivities.remove(activity);
		
		schedule.add(entry);
	}
	
	public void wakeUpEverybody(){
		for (Person person : allPersons.values()){ 
			if (person.needsToSleep()) {
				person.goToSleep();
			} else if (person.needsToWakeUp()){
				person.wakeUp();
			}
		}

	}
	
	public void sendOtherPeopleToSleep(){
		for (Person person : availablePersons.values()){
			if (person.getName().equalsIgnoreCase("p2")){
				System.out.println("Sending idle people to sleep in hour: " + currentHour);
			}
			person.goToSleep();
		}
	}
	
	public void passOneHour(){
		currentHour++;
		
		System.out.println("Before updating: ");
		System.out.println(allPersons.get("P2"));
		
		for (Person person : allPersons.values()){ 

//			if (person.isSleeping() == null || (!person.isSleeping() && !person.hasCurrentActivity())) {
//				person.goToSleep();
//			}
			
			if (person.isSleeping() || person.hasCurrentActivity() == null || !person.hasCurrentActivity()){
				person.setCurrentSleepTime(person.getCurrentSleepTime() + 1);
			} else {
				person.setCurrentAwakeTime(person.getCurrentAwakeTime() + 1);
			}
			
		}
		
		for (Activity activity : allActivities.values()){
			
			if (activity.isOpen() == null){
				activity.closeForMaintenance();
			}
			
			if (activity.needsMaintenance()){
				activity.closeForMaintenance();
			} else if (activity.needsToBeReopened()){
				activity.openForPersons();
			}
			
			if (activity.isOpen()){
				activity.setCurrentOpenHours(activity.getCurrentOpenHours() + 1);
			} else {
				activity.setCurrentMaintenanceHours(activity.getCurrentMaintenanceHours() + 1);
			}
		}
		
		System.out.println("*********************");
		System.out.println("Hour " + (currentHour - 1) + " passed");
		System.out.println(allPersons.get("P2"));
	}
	
	public void refreshStatus(){
		
		System.out.println("Refreshing status");
		
		availablePersons.clear();
		availableActivities.clear();
		
		//wakeUpEverybody();
		
		for (Person p : allPersons.values()){
			if (!p.isSleeping()){
				availablePersons.put(p.getName(), p);
			}
		}
		
		for (Activity a : allActivities.values()){
			if (a.isOpen()){
				availableActivities.put(a.getName(), a);
			}
		}
		
	}
	
	public void validateActivities(){

		Boolean hasInvalidActivities = false;
		for (Schedule s : schedule){
			Activity activity = s.getActivity();
			System.out.println("Activity: " + activity);

			Integer min = allActivities.get(activity.getName()).getMinNoOfParticipants();
			Integer max = allActivities.get(activity.getName()).getCapacity();

			if (min > s.getPersons().size() || max < s.getPersons().size()) {
				hasInvalidActivities = true;
				System.out.println("Invalid activity: " + activity.getName() + ", hour: " + s.getHour() + ", min: " + min + ", max: " + max + ", current: " + s.getPersons().size());
			}
			
		}
		
		System.out.println("Invalid activities? " + hasInvalidActivities);
		
	}
	
	public void schedule(){
		makeAllPersonsAvailable();
		makeAllActivitiesAvailable();
		
		while (currentHour <= journey.getDuration()) {
			System.out.println("Starting hour: " + currentHour + " with " + availablePersons.size() + " persons and " + availableActivities.size() + " activities");
			
			Activity activity = chooseBestActivity();
			while ((activity != null && availablePersons.size() != 0)){
				scheduleActivity(currentHour, activity.getName(), getPersonsWhoLikeActivity(activity));
				activity = chooseBestActivity();
			}
			
			sendOtherPeopleToSleep();
			passOneHour();
			wakeUpEverybody();
			refreshStatus();
			
			//printCurrentSchedule();
			
		}
		
		Float currentProfit = 0F;
		for (Schedule s : schedule){
			currentProfit += s.getProfit();
		}
		
		Float max = getMaximumProfit();
		
		System.out.println("Max: " + max + ", current: " + currentProfit);
		
		//validateActivities();
		
		OutputWriter.write(generateSchedule(), currentProfit);

		writeScheduleToEXCELFile();
	}
	
	
	/**********************************************************************/
	
	public List<ScheduleEntry> generateSchedule(){
		
		System.out.println("-----------------------------");
		
		List<ScheduleEntry> list = new ArrayList<ScheduleEntry>();
		Map<String, List<Integer>> personToHours = new HashMap<String, List<Integer>>();
		
		for (String person : allPersons.keySet()){
			personToHours.put(person, new ArrayList<Integer>());
		}
		
		for (Schedule s : schedule){
		//	System.out.println(s);
			for (String p : s.getPersons()){
				ScheduleEntry se = new ScheduleEntry();
				se.setActivity(s.getActivity().getName());
				se.setHour(s.getHour());
				se.setProfit(s.getProfit());
				se.setPerson(p);
				list.add(se);
				List<Integer> temp = personToHours.get(p);
				temp.add(s.getHour());
				personToHours.put(p, temp);
			}
		}
		
		for (String person : allPersons.keySet()){
			for (int i = 0; i < journey.getDuration(); i++){
				if (!personToHours.get(person).contains(i)){
					ScheduleEntry se = new ScheduleEntry();
					se.setHour(i);
					se.setPerson(person);
					se.setProfit(0F);
					list.add(se);
				}
			}
		}
		
		return list;
	}
	
	public void writeOutput() {
		
	}
	
	public void writeScheduleToEXCELFile(){
	    Workbook wb = new XSSFWorkbook();
	    Sheet sheet = wb.createSheet("new sheet");

	    int rowNum = 0;
	    int cellNum = 0;
	    
	    Row row = sheet.createRow(rowNum);
	    // Create a cell and put a value in it.
	    Cell cell = row.createCell(cellNum);
	    cell.setCellValue("HOUR");
        cellNum = cellNum + 1;
	    // Or do it on one line.
	    row.createCell(cellNum).setCellValue("ACTIVITY");
	    cellNum = cellNum+1;
	    row.createCell(cellNum).setCellValue("Profit");
	    cellNum = cellNum+1;
	    row.createCell(cellNum).setCellValue("Persons");

	    
	    for (Schedule s : schedule){
	    	row = sheet.createRow(rowNum+1);
	    	rowNum = rowNum + 1;
	    	cellNum = 0;
	    	row.createCell(cellNum).setCellValue(s.getHour());
	    	cellNum = cellNum+1;
	    	row.createCell(cellNum).setCellValue(s.getActivity().getName());
	    	cellNum = cellNum+1;
	    	row.createCell(cellNum).setCellValue(s.getProfit());
	    	cellNum = cellNum+1;
			
			for (String p : s.getPersons()) {
				row = sheet.createRow(rowNum+1);
		    	rowNum = rowNum + 1;
		    	row.createCell(cellNum).setCellValue(p);		
		    	row.createCell(cellNum + 1).setCellValue(allPersons.get(p).toString());
			}
		}
	    
	    // Create a row and put some cells in it. Rows are 0 based.
	    
	    // Write the output to a file
	    FileOutputStream fileOut;
		try {
			fileOut = new FileOutputStream("D:\\lEMON\\workbook.xls");
		
			wb.write(fileOut);
			fileOut.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
}
